package ac.aiit.tw;

import gnu.io.CommPortIdentifier;
import gnu.io.NoSuchPortException;
import gnu.io.PortInUseException;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import gnu.io.UnsupportedCommOperationException;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.TooManyListenersException;


public class serialCommand {
	public static class serialCommException extends Exception{
		private static final long serialVersionUID = -8941657852607282628L;

		public serialCommException() {
			super();
		}

		public serialCommException(String message, Throwable cause) {
			super(message, cause);
		}

		public serialCommException(String message) {
			super(message);
		}

		public serialCommException(Throwable cause) {
			super(cause);
		}
	}
	public static interface ICommMessageListener{
		public void onMessage(String text) throws serialCommException;
	}
	private SerialPort _port = null;
	private Runnable _portCloser = null;
	private messageListenerManager _listener = null;
	private serialCommand(){
		super();
	}
	public static final serialCommand getInstance(String id) throws serialCommException{
		try {
			serialCommand ret = new serialCommand();
			ret.openPort(id);
			ret.initPort();
			ret.initListener();
			return ret;
		} catch (NoSuchPortException e) {
			throw new serialCommException(e);
		} catch (PortInUseException e) {
			throw new serialCommException(e);
		} catch (UnsupportedCommOperationException e) {
			throw new serialCommException(e);
		} catch (TooManyListenersException e) {
			throw new serialCommException(e);
		}
	}
	private final void _close(){
		if(null != this._port){
			System.out.println("closing port...");
			this._port.close();
			this._port = null;
			System.out.println("port closed");
		}
	}
	private final void _addHook(){
		if(null == this._portCloser){
			this._portCloser = new Runnable(){
				@Override
				public void run() {
					_close();
				}
			};
			Runtime.getRuntime().addShutdownHook(new Thread(this._portCloser));
		}
	}
	private synchronized void openclose(String id) throws NoSuchPortException, PortInUseException{
		if(null != id){
			this._close();
			CommPortIdentifier _portId = CommPortIdentifier.getPortIdentifier(id);
			this._port = (SerialPort)_portId.open(this.getClass().getName(), 2000);
			this._addHook();
		}else{
			this._close();
		}
	}

	private void openPort(String id) throws NoSuchPortException, PortInUseException{
		this.openclose(id);
	}
	private void initPort() throws UnsupportedCommOperationException{
		this._port.setSerialPortParams(
				38400
				, SerialPort.DATABITS_8
				, SerialPort.STOPBITS_1
				, SerialPort.PARITY_NONE);
		this._port.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
	}
	private static class messageListenerManager implements SerialPortEventListener{
		private HashMap<Long, ICommMessageListener> _listeners = new HashMap<Long, ICommMessageListener>();
		private SerialPort _port = null;
		private long _lstId = 0;
		public messageListenerManager(serialCommand parent){
			super();
			this._port = parent._port;
		}
		private synchronized Long addremoveListener(Long id, ICommMessageListener listener){
			if(null == id){
				this._lstId ++;
				this._listeners.put(this._lstId, listener);
				return this._lstId;
			}else if(null == listener){
				this._listeners.remove(id);
			}else{
				this._listeners.put(id, listener);
			}
			return id;
		}
		public Long addListener(ICommMessageListener listener){
			return this.addremoveListener(null, listener);
		}
		public void removeListener(Long id){
			this.addremoveListener(id, null);
		}
		public void updateListener(Long id, ICommMessageListener listener){
			this.addremoveListener(id, listener);
		}

		@Override
		public void serialEvent(SerialPortEvent evt) {
			if(SerialPortEvent.DATA_AVAILABLE == evt.getEventType()){
				InputStream is = null;
				try {
					is = this._port.getInputStream();
					byte[] buf = new byte[1024];
					int read = 0;
					while(0 <= (read  = is.read(buf))){
						if(0 == read){
							try {
								Thread.sleep(100);
							} catch (InterruptedException e) {
							}
						}else{
							String msg  = new String(buf);
							for(ICommMessageListener listener : this._listeners.values()){
								try {
									listener.onMessage(msg);
								} catch (serialCommException e) {
									e.printStackTrace();
								}
							}
						}
					}
				} catch (IOException e) {
					e.printStackTrace();
				}finally{
					if(null != is){
						try {
							is.close();
						} catch (IOException e) {
						}
					}
				}
			}
		}
	}
	private void initListener() throws TooManyListenersException{
		if(null == this._listener){
			messageListenerManager listener = new messageListenerManager(this);
			this._port.addEventListener(listener);
			this._port.notifyOnDataAvailable(true);
			this._listener = listener;
		}
	}
	
	public Long addMesageListener(ICommMessageListener listener){
		return this._listener.addListener(listener);
	}
	
	public void write(int val) throws IOException{
		OutputStream os = null;
		try{
			os = this._port.getOutputStream();
			os.write(val);
		}finally{
			if(null != os){
				try{
					os.flush();
					os.close();
				}catch(IOException e){
					e.printStackTrace();
				}
			}
		}
	}
	public void write(String test) throws IOException{
		OutputStream os = null;
		try{
			os = this._port.getOutputStream();
			os.write(test.getBytes());
		}finally{
			if(null != os){
				try{
					os.flush();
					os.close();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		}
	}
	public void close(){
		try {
			this.openclose(null);
		} catch (NoSuchPortException e) {
			e.printStackTrace();
		} catch (PortInUseException e) {
			e.printStackTrace();
		}
	}

}
